package cn.hill4j.tool.core.obj.collect;

import java.util.*;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.function.Predicate;
/**
  * ClassName: GroupTools
  * Description: 集合分组工具类
  * @author hillchen
  * Date: 2023/8/7
  * @version: 1.0
  */
public class GroupTools {
    /**
     * 集合元素分组
     * @param <S> 源对象类型
     * @param <K> 目标分组健值类型
     * @param <T> 目标对象类型
     * @param source 源集合
     * @param converter 对象转换器
     * @param keyGetter 健值获取器
     * @return Map
     */
    public static <S,K,T> Map<K,List<T>> groupByTarget(Collection<S> source,  Function<S,T> converter,Function<T,K> keyGetter){
        return new ToGroupContext<>(source,converter,keyGetter).group();
    }


    /**
     * 集合类型转换
     * @param <S> 源对象类型
     * @param <K> 分组key值类型
     * @param source 源集合
     * @param keyGetter 健值获取器
     * @return Map
     */
    public static <S,K> Map<K,List<S>> group(Collection<S> source, Function<S,K> keyGetter){
        return new ToGroupContext<>(keyGetter,source,Function.identity()).group();
    }


    /**
     * 集合类型转换
     * @param <S> 源对象类型
     * @param <K> 分组key值类型
     * @param <T> 目标对象类型
     * @param source 源集合
     * @param converter 对象转换器
     * @param keyGetter 健值获取器
     * @return Map
     */
    public static <S,K,T> Map<K,List<T>> group(Collection<S> source,  Function<S,T> converter,Function<S,K> keyGetter){
        return new ToGroupContext<>(keyGetter,source,converter).group();
    }

    /**
     * 集合类型转换
     * @param <S> 源对象类型
     * @param <K> 分组key值类型
     * @param <T> 目标对象类型
     * @param source 源集合
     * @param converter 对象转换器
     * @param keyGetter 健值获取器
     * @return Map
     */
    public static <S,K,T> Map<K,List<T>> group(Collection<S> source,  Function<S,T> converter,BiFunction<S,T,K> keyGetter){
        return new ToGroupContext<>(source,converter,keyGetter).group();
    }

    /**
     * 集合元素分组上下文
     * @param <S> 源对象类型
     * @param <K> 目标分组健值类型
     * @param <T> 目标对象类型
     */
    public static class ToGroupContext<S, K,T> {
        /**
         * 源集合
         */
        private final Collection<S> source;
        /**
         * 对象转换器
         */
        private final Function<S,T> converter;
        /**
         * 健值获取器
         */
        private final BiFunction<S,T,K> keyGetter;
        /**
         * 目标对象排序方法
         */
        private Comparator<T> sort;
        /**
         * 元素过滤器
         */
        private BiPredicate<S,T> elementExcluder = CollectionConstant.NO_BI_EXCLUDER;
        /**
         * 元素过滤器
         */
        private Predicate<K> keyExcluder = CollectionConstant.NO_EXCLUDER;

        public ToGroupContext( Function<S, K> keyGetter,Collection<S> sources, Function<S, T> converter) {
            this(sources,converter,(e,t) -> keyGetter.apply(e));
        }
        public ToGroupContext(Collection<S> sources, Function<S, T> converter, Function<T, K> keyGetter) {
            this(sources,converter,(e,t) -> keyGetter.apply(t));
        }
        public ToGroupContext(Collection<S> sources, Function<S, T> converter, BiFunction<S,T, K> keyGetter) {
            this.source = sources;
            this.converter = converter;
            this.keyGetter = keyGetter;
        }

        public ToGroupContext<S, K,T> setElementExcluder(BiPredicate<S, T> elementExcluder) {
            this.elementExcluder = elementExcluder;
            return this;
        }

        public ToGroupContext<S, K,T> setSort(Comparator<T> sort) {
            this.sort = sort;
            return this;
        }

        public Map<K,List<T>> group(){
            if (CollectionTools.isEmpty(source)){
                return Collections.emptyMap();
            }
            Map<K,List<T>> result = new HashMap<>();
            for (S s: source){
                T t = converter.apply(s);
                if (!elementExcluder.test(s,t)){
                    K key = keyGetter.apply(s,t);
                    if (!keyExcluder.test(key)){
                        List<T> ts = CollectionTools.getOrDefaultAndPut(result,key,() -> new ArrayList<>());
                        ts.add(t);
                    }
                }
            }
            if (Objects.nonNull(sort)){
                for (List<T> val : result.values()){
                    val.sort(sort);
                }
            }
            return result;
        }
    }
}
